<?php
/**
 * Auther: Joshua Conero
 * Date: 2017/8/3 0003 15:25
 * Email: brximl@163.com
 * Name: 访客ip黑名单
 */

namespace app\common;


use think\facade\Config;
use think\Db;

class VipBlacklist
{
    private $date; // 当前日期
    private $visitip;   // 请求ip
    private $sTime = 30;  // 标准时间默认5分钟内
    private $sCount = 5;// 标准次数
    private $sMaxCount = 40;    // 标准日最大次数，到达是直接当天锁死
    public function __construct($config=array())
    {
        $this->date = date('Y-m-d');
        $this->visitip = request()->ip();
        if(isset($config['ip'])) $this->visitip = $config['ip'];
        if(isset($config['sTime'])) $this->sTime = $config['sTime'];
        if(isset($config['sCount'])) $this->sCount = $config['sCount'];
        if(isset($config['sMaxCount'])) $this->sMaxCount = $config['sMaxCount'];
    }

    /**
     * ip地址检测： 超过最大日可访问次数直接锁死
     * @param null|string $ip
     * @return array    [bool, string] 元组
     */
    public function check($ip=null){
        $ret = array(false, '');
        Db::startTrans();
        try{
            $ret = $this->_check($ip);
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
            $br = "\r\n";
            $msg = $br .'(VipBlacklist): ip地址安全检测时出错'
                . $br . $e->getMessage()
                . $br . $e->getTraceAsString()
                ;
            debugOut($msg);
        }
        return $ret;
    }
    /**
     * ip地址检测： 超过最大日可访问次数直接锁死
     * @param null|string $ip
     * @return array    [bool, string] 元组
     */
    public function _check($ip=null){
        $isLock = false;$msg = '';
        if($ip) $this->visitip = $ip;
        else $ip = $this->visitip;
        if(empty($ip)) return array($isLock, $msg);
        // 最大访问数检测
        $bind = [null, null, null, null];
        $bind[0] = $ip;
        $bind[1] = $this->sCount;
        $bind[2] = $this->sTime;
        $bind[3] = $this->sMaxCount;
        $res = Db::query('call vip_blacklist_ck(?, ?, ?, ?)',$bind);
        $res = isset($res[0])? $res[0][0]:['code'=>0, 'msg' => '', 'vid'=>''];
        $pid = isset($res['vid']) && $res['vid']? $res['vid']: null;
        $num = isset($res['num'])? $res['num']: 0;
        switch ($res['code']){
            case 1000:      // 死锁态
                $isLock = true;
                $msg = '由于您过度频繁的请求网站，为了保护网站的安全，当天网站将停止为您维护，请您谅解。
                同时你应该停止这种行为，网站可能会记录您的这种不良行为，以及您的网站体验可能受限('.$num.'/'.$this->sMaxCount.')。';
                $msg = $this->checkMsg($msg);
            break;
            case 1001:      // 锁定状态
                $isLock = true;
                if(empty($pid)){ // 新增数据
                    $pid = Db::table('vst_blackip')
                        ->insertGetId([
                            'date' => date('Y-m-d'),
                            'ip'   => $ip,
                            'lock_mtime'    => time(),
                            'lock_mk'       => 'Y',
                            'lock_dtime'    => 30
                        ]);
                }else{
                    $lockDtime = floor($num / $bind[1]) + $bind[2];
                    Db::table('vst_blackip')
                        ->where(['listid'=>$pid, 'lock_mk'=>'N'])
                        ->update(['lock_mtime'=>time(), 'lock_mk'=>'Y', 'lock_dtime'=>$lockDtime])
                    ;
                }
                $msg = '由于您过度频繁的请求网站，为保护网站的安全，您的访问将被锁定(30min)。
                请杜绝这种行为，此操作可能引起当天访问被直接锁死('.$num.'/'.$this->sMaxCount.')!
                ';
                $msg = $this->checkMsg($msg);
                break;
            case 2000:      // 请求有效
                if(empty($pid)){ // 新增数据
                    $pid = Db::table('vst_blackip')
                        ->insertGetId([
                            'date' => date('Y-m-d'),
                            'ip'   => $ip,
                            'lock_mk'       => 'N'
                        ]);
                }
                if($num > 0){
                    $msg = '您今天是第几次访问数据，当时系统将会对数据进行过滤('.$num.').';
                    $msg = $this->checkMsg($msg);
                }
                break;
        }
        if($pid) $this->addVisitLog($pid);
        //debugOut($res);
        return array($isLock, $msg);
    }

    /**
     * @param $msg string 消息
     * @return mixed string
     */
    private function checkMsg($msg){
        return '
            <style>
                body{background-color: black;}
            </style>
            <div style="color: red;margin-left: 10%;margin-top:5%;">
                <br/> .@message ------ '.$msg.'
                <br/> .@time --------- '.date('Y-m-d H:i:s').'
                <br/> .@version ------ v'.Config::get('setting.version').'
                <br/> .@build -------- '.Config::get('setting.build').'
                <br/> .@author ------- '.Config::get('setting.author_en').'
                <br/> .@email -------- '.Config::get('setting.email').'
                <br/> .@QQ ----------- '.Config::get('setting.QQ').'
          '
        ;
    }

    /**
     * 新增子日志记录
     * @param $pid string|callback  父键id
     * @param $arr array|null  父键id
     * @return int|null|string
     */
    public function addVisitLog($pid, $arr=null){
        if($pid){
            $pid = ($pid instanceof \Closure)? call_user_func($pid):$pid;
            if(empty($pid)) return null;
            $data = [
                'pid'   => $pid,
                'mtime' => date('Y-m-d H:i:s')
            ];
            if(isset($_SERVER['HTTP_USER_AGENT'])) $data['agent'] = $_SERVER['HTTP_USER_AGENT'];
            if($arr) $data = array_merge($data, $arr);
            $header = getallheaders();
            if($header) $data['param'] = json_encode($header);
            return Db::table('vst_blackip_logs')
                ->insert($data)
                ;
        }
        return null;
    }
}